// TestRunnerWnd.cpp : Implementation of CTestRunnerWnd

#include "stdafx.h"
#include <olectl.h>
#include "TestRunnerWnd.h"
#include "MultiLinePropertyHandler.h"

// CTestRunnerWnd
LRESULT CTestRunnerWnd::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	bHandled = TRUE;

	m_csTestProjects = GetDlgItem(IDC_CB_TESTPROJECTS);
	m_csTestRunList = GetDlgItem(IDC_TESTRUNLIST);
	m_btRefresh = GetDlgItem(IDC_BT_REFRESH);
	m_btDebugAll = GetDlgItem(IDC_BT_DEBUGALL);
	m_btDebugChecked = GetDlgItem(IDC_BT_DEBUGCHECKED);
	m_btRunAll = GetDlgItem(IDC_BT_RUNALL);
	m_btRunChecked = GetDlgItem(IDC_BT_RUNCHECKED);

	m_csTestRunList.SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);
	
	LVCOLUMN stColumn;
	stColumn.mask = LVCF_WIDTH;
	stColumn.cx = 48;
	m_csTestRunList.SendMessage(LVM_INSERTCOLUMN, ciImage, (LPARAM)&stColumn);

	stColumn.mask = LVCF_WIDTH | LVCF_TEXT;
	stColumn.cx = 100;
	
	stColumn.pszText = _T("name");
	m_csTestRunList.SendMessage(LVM_INSERTCOLUMN, ciName, (LPARAM)&stColumn);

	stColumn.cx = 250;
	stColumn.pszText = _T("report");
	m_csTestRunList.SendMessage(LVM_INSERTCOLUMN, ciReport, (LPARAM)&stColumn);

	stColumn.cx = 70;
	stColumn.pszText = _T("status");
	m_csTestRunList.SendMessage(LVM_INSERTCOLUMN, ciStatus, (LPARAM)&stColumn);

	stColumn.cx = 60;
	stColumn.pszText = _T("time");
	m_csTestRunList.SendMessage(LVM_INSERTCOLUMN, ciTime, (LPARAM)&stColumn);

	SetWindowLong(GWL_USERDATA, (LONG)GetCurrentProcessId());

	FillTestProjects();
	FillTestRunTree();

	return CComCompositeControl::OnInitDialog(uMsg, wParam, lParam, bHandled);
}

LRESULT CTestRunnerWnd::OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled)
{
	WORD wWidth = LOWORD(lParam);
	WORD wHeight = HIWORD(lParam);

	m_csTestProjects.SetWindowPos(0, 0, 0, wWidth, 21, SWP_NOZORDER | SWP_NOMOVE);
	m_csTestRunList.SetWindowPos(0, 0, 0, wWidth, wHeight - 55, SWP_NOZORDER | SWP_NOMOVE);

	bHandled = TRUE;
	return 0;
}

void CTestRunnerWnd::Prepare(EnvDTE80::DTE2* pDTE, IExchangeControl *pIExchangeControl)
{
	m_spDTE = pDTE;
	m_pIExchangeControl = pIExchangeControl;
}

void CTestRunnerWnd::FillTestProjects()
{
	ATLASSERT(m_csTestProjects.m_hWnd);
	ATLASSERT(m_spDTE);
	
	if ((m_csTestProjects.m_hWnd) && (m_spDTE))
	{
		int nCurSel = static_cast<int>(m_csTestProjects.SendMessage(CB_GETCURSEL));
		CAtlStringW wstr;
		if (CB_ERR != nCurSel)
			wstr = m_aTestProjects.GetAt(nCurSel);

		nCurSel = CB_ERR;
		m_aTestProjects.RemoveAll();
		m_csTestProjects.SendMessage(CB_RESETCONTENT);

		long nCount = 0, nTestProjectCount = 0;
		CComBSTR bstr, bstrOriginalName, bstrOriginalUniqueName;
		EnvDTE::_SolutionPtr spSolution;
		if (SUCCEEDED(m_spDTE->get_Solution(&spSolution)) &&
			SUCCEEDED(spSolution->get_Count(&nCount)))
		{
			CComVariant vtVCCLCompilerTool(L"VCCLCompilerTool");
			for(CComVariant vt(1);vt.lVal <= nCount; ++vt.lVal)
			{
				EnvDTE::ProjectPtr spProject;
				EnvDTE::PropertiesPtr spProperties;
				VCProjectEngineLibrary::VCProjectPtr spVCProject;
				IDispatchPtr spDispatch;
				VCProjectEngineLibrary::IVCCollectionPtr spVCConfigurations, spTools;
				VCProjectEngineLibrary::VCConfigurationPtr spVCConfiguration;
				VCProjectEngineLibrary::VCCLCompilerToolPtr spVCCLCompilerTool;
				CComBSTR bstrName, bstrUniqueName;

				if (SUCCEEDED(spSolution->Item(vt, &spProject)) &&
					SUCCEEDED(spProject->get_Object(&spDispatch)) &&
					(spVCProject = spDispatch) && //C++ project
					SUCCEEDED(spVCProject->get_Configurations(&spDispatch)) &&
					(spVCConfigurations = spDispatch) &&
					SUCCEEDED(spVCConfigurations->Item(CComVariant(1), &spDispatch)) && //use first configuration
					(spVCConfiguration = spDispatch) &&
					SUCCEEDED(spVCConfiguration->get_Tools(&spDispatch)) &&
					(spTools = spDispatch) &&
					SUCCEEDED(spTools->Item(vtVCCLCompilerTool, &spDispatch)) &&
					(spVCCLCompilerTool = spDispatch) &&
					SUCCEEDED(spVCCLCompilerTool->get_PreprocessorDefinitions(&bstr)) &&
					SUCCEEDED(spProject->get_Name(&bstrName)) &&
					SUCCEEDED(spProject->get_UniqueName(&bstrUniqueName)))
				{
					CMultiLinePropertyHandler cs(bstr);
					if (cs.IsDefined(L"_UT"))
					{
						++nTestProjectCount;
	#ifdef _UNICODE
						m_csTestProjects.SendMessage(CB_ADDSTRING, 0, (LPARAM)bstrName.m_str);
	#else
						CAtlString str(bstrName);
						m_csTestProjects.SendMessage(CB_ADDSTRING, 0, (LPARAM)str.GetString());
	#endif
						int n = static_cast<int>(m_aTestProjects.Add(bstrUniqueName));
						if ((CB_ERR == nCurSel) && (wstr == bstrUniqueName))
							nCurSel = n;
					} //if (cs.IsDefined(L"_UT"))
				}
			} //for(CComVariant vt(1);vt.lVal <= nCount; ++vt.lVal)
		}

		if (CB_ERR != nCurSel)
			m_csTestProjects.SendMessage(CB_SETCURSEL, nCurSel);
		else if (nTestProjectCount)
			m_csTestProjects.SendMessage(CB_SETCURSEL, 0);
		else
			m_csTestRunList.SendMessage(LVM_DELETEALLITEMS);
	}
}

BOOL CTestRunnerWnd::GetCurSelTestProject(OUT EnvDTE::ProjectPtr & spCurSelProject)
{
	//since there is no tracking for solution events 
	//there is a need to define that the project is still in the solution
	
	ATLASSERT(m_csTestProjects.m_hWnd);
	ATLASSERT(m_spDTE);

	spCurSelProject = NULL;
	
	if ((m_csTestProjects.m_hWnd) && (m_spDTE))
	{
		int nCurSel = static_cast<int>(m_csTestProjects.SendMessage(CB_GETCURSEL));
		CAtlStringW wstr;
		if (CB_ERR != nCurSel)
		{
			wstr = m_aTestProjects.GetAt(nCurSel);
			long nCount = 0;
			EnvDTE::_SolutionPtr spSolution;
			if (SUCCEEDED(m_spDTE->get_Solution(&spSolution)) &&
				SUCCEEDED(spSolution->get_Count(&nCount)))
			{
				for(CComVariant vt(1);vt.lVal <= nCount; ++vt.lVal)
				{
					EnvDTE::ProjectPtr spProject;
					CComBSTR bstrUniqueName;

					if (SUCCEEDED(spSolution->Item(vt, &spProject)) &&
						SUCCEEDED(spProject->get_UniqueName(&bstrUniqueName)) &&
						(bstrUniqueName == wstr.GetString()))
					{
						spCurSelProject = spProject;
						break;
					}
				} //for(CComVariant vt(1);vt.lVal <= nCount; ++vt.lVal)
			}
		}
	}

	return spCurSelProject != NULL;
}

void CTestRunnerWnd::FillTestRunTree()
{
	EnvDTE::ProjectPtr spCurSelProject;
	TestProject csTestProjectTree;
	if (GetCurSelTestProject(spCurSelProject) &&
		csTestProjectTree.Prepare(spCurSelProject))
	{
		m_aGroups.Copy(csTestProjectTree.GetTestGroups());
		Synchronize();
	}
}

LRESULT CTestRunnerWnd::OnCbnSelchangeCbTestprojects(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	FillTestRunTree();
	return 0;
}

LRESULT CTestRunnerWnd::OnBnClickedBtRefresh(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	EndNotification();

	FillTestProjects();
	FillTestRunTree();

	return 0;
}

LRESULT CTestRunnerWnd::OnBnClickedBtRunall(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	Start(FALSE, TRUE);
	return 0;
}

LRESULT CTestRunnerWnd::OnBnClickedBtRunchecked(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	Start(FALSE, FALSE);
	return 0;
}

LRESULT CTestRunnerWnd::OnBnClickedBtDebugall(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	Start(TRUE, TRUE);
	return 0;
}

LRESULT CTestRunnerWnd::OnBnClickedBtDebugchecked(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
	Start(TRUE, FALSE);
	return 0;
}

int CTestRunnerWnd::Synchronize(WORD nSelGroup/* = 0xFFFF*/)
{
	int nRet = -1;
	const CAtlArray<TestGroupRun> & rGroups = GetTestGroups();
	m_csTestRunList.SendMessage(LVM_DELETEALLITEMS);
	
	LVITEMW stItem;
	stItem.iItem = 0;
	for (size_t n = 0; n < rGroups.GetCount(); ++n)
	{
		const TestGroupRun & rGroup = rGroups.GetAt(n);

		stItem.mask = LVIF_PARAM;
		stItem.iSubItem = ciImage;
		stItem.lParam = MAKELONG(n, 0xFFFF);

		if (nSelGroup == n)
		{
			stItem.mask = LVIF_PARAM | LVIF_STATE;
			stItem.stateMask = LVIS_SELECTED;
			stItem.state = LVIS_SELECTED;

			nRet = (int)m_csTestRunList.SendMessage(LVM_INSERTITEMW, 0, (LPARAM)&stItem);
		}
		else
		{
			m_csTestRunList.SendMessage(LVM_INSERTITEMW, 0, (LPARAM)&stItem);
		}

		stItem.mask = LVIF_TEXT;

		stItem.iSubItem = ciName;
		stItem.pszText = const_cast<LPWSTR>(rGroup.wstrName.GetString());
		m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);

		stItem.iSubItem = ciReport;
		stItem.pszText = const_cast<LPWSTR>(rGroup.wstrReport.GetString());
		m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);
		//status
		stItem.iSubItem = ciStatus;
		stItem.pszText = const_cast<LPWSTR>(GetRunStatusName(rGroup.eStatus));
		m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);
		//time
		CAtlStringW wstr;
		CTimeSpan sp(rGroup.tDuration);
		if (rGroup.tDuration)
			wstr.Format(L"%02i:%02i:%02i", sp.GetHours(), sp.GetMinutes(), sp.GetSeconds());
		stItem.iSubItem = ciTime;
		stItem.pszText = const_cast<LPWSTR>(wstr.GetString());
		m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);
		
		++stItem.iItem;
		if (rGroup.bExpanded)
		{
			for (size_t n1 = 0; n1 < rGroup.aTests.GetCount(); ++n1)
			{
				const TestRun & rTestRun = rGroup.aTests.GetAt(n1);
				stItem.mask = LVIF_PARAM;
				stItem.iSubItem = ciImage;
				stItem.lParam = MAKELONG(n, n1);
				
				m_csTestRunList.SendMessage(LVM_INSERTITEMW, 0, (LPARAM)&stItem);

				stItem.mask = LVIF_TEXT;

				stItem.iSubItem = ciName;
				stItem.pszText = const_cast<LPWSTR>(rTestRun.wstrName.GetString());
				m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);

				stItem.iSubItem = ciReport;
				stItem.pszText = const_cast<LPWSTR>(rTestRun.wstrReport.GetString());
				m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);
				//status
				stItem.iSubItem = ciStatus;
				stItem.pszText = const_cast<LPWSTR>(GetRunStatusName(rTestRun.eStatus));
				m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);
				//time
				CStringW wstr1;
				CTimeSpan sp1(rTestRun.tDuration);
				if (rTestRun.tDuration)
					wstr1.Format(L"%02i:%02i:%02i", sp1.GetHours(), sp1.GetMinutes(), sp1.GetSeconds());
				stItem.iSubItem = ciTime;
				stItem.pszText = const_cast<LPWSTR>(wstr1.GetString());
				m_csTestRunList.SendMessage(LVM_SETITEMW, 0, (LPARAM)&stItem);

				++stItem.iItem;
			}
		} // if (rGroup.bExpanded)

		if (n + 1 < rGroups.GetCount())
		{
			//blank line
			stItem.mask = LVIF_PARAM;
			stItem.iSubItem = ciImage;
			stItem.lParam = MAKELONG(0xFFFF, 0xFFFF);
			
			m_csTestRunList.SendMessage(LVM_INSERTITEM, 0, (LPARAM)&stItem);
			++stItem.iItem;
		}
	}

	return nRet;
}

LRESULT CTestRunnerWnd::OnSubItemDraw(LPNMCUSTOMDRAW pNMCD)
{
	LRESULT nResult = CDRF_DODEFAULT;
	LPNMLVCUSTOMDRAW p = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMCD);
	WORD nItem = HIWORD(pNMCD->lItemlParam);
	WORD nGroup = LOWORD(pNMCD->lItemlParam);

	if (0xFFFF == nGroup)
	{ //blank line
		nResult = CDRF_SKIPDEFAULT;
	} else
	{
		const TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);

		if (p->iSubItem == ciImage)
		{
			RECT rc;
			rc.top = p->iSubItem;
			rc.left = LVIR_BOUNDS;
			m_csTestRunList.SendMessage(LVM_GETSUBITEMRECT, pNMCD->dwItemSpec, (LPARAM)&rc);

			int nCheckIdx;
			int nStatusIdx;
			if (0xFFFF == nItem)
			{ //group
				ImageList_Draw(m_imgStatusList, rGroup.bExpanded ? iBoxExpanded : iBoxCollapsed, 
					pNMCD->hdc, rc.left, rc.top, ILD_NORMAL);
				nCheckIdx = rGroup.bDisabled ? (rGroup.bChecked ? iBoxDisabledChecked : iBoxDisabledUnchecked) : (rGroup.bChecked ? iBoxChecked : iBoxUnchecked);
				nStatusIdx = rGroup.eStatus;
			} else 
			{ //test run
				const TestRun & rTestRun = rGroup.aTests.GetAt(nItem);
				nCheckIdx = rTestRun.bDisabled ? (rTestRun.bChecked ? iBoxDisabledChecked : iBoxDisabledUnchecked) : (rTestRun.bChecked ? iBoxChecked : iBoxUnchecked);
				nStatusIdx = rTestRun.eStatus;
			}

			ImageList_Draw(m_imgStatusList, nCheckIdx, pNMCD->hdc, rc.left + 16, rc.top, ILD_NORMAL);
			ImageList_Draw(m_imgStatusList, nStatusIdx, pNMCD->hdc, rc.left + 32, rc.top, ILD_NORMAL);

			nResult = CDRF_SKIPDEFAULT;
		} else 
		{
			if (0xFFFF == nItem)
			{ //test group
				if (rGroup.bDisabled)
				{	//item is grayed
					p->clrText = RGB(100, 100, 100);
					p->clrTextBk = RGB(200, 200, 200);
				} else 
				{
					p->clrText = RGB(0, 0, 0);
					p->clrTextBk = RGB(200, 200, 200);
				}
			} else 
			{ //test run
				const TestRun & rTestRun = rGroup.aTests.GetAt(nItem);
				if (rTestRun.bDisabled)
				{	//item is grayed
					p->clrText = RGB(100, 100, 100);
					p->clrTextBk = RGB(230, 230, 230);
				} else 
				{
					p->clrText = RGB(0, 0, 0);
					p->clrTextBk = nItem & 1? RGB(255, 255, 255) : RGB(220, 210, 160);
				}
			}
		}
	}

	return nResult;
}


LRESULT CTestRunnerWnd::OnNMCustomdrawTestRunList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& bHandled)
{
	bHandled = TRUE;
	LRESULT nRet = CDRF_DODEFAULT;
	LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);

	switch (pNMCD->dwDrawStage)
	{
	case CDDS_PREPAINT:
		nRet = CDRF_NOTIFYITEMDRAW;
		break;
	case CDDS_ITEMPREPAINT:
		nRet = CDRF_NOTIFYSUBITEMDRAW;
		break;
	case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
		nRet = OnSubItemDraw(pNMCD);
		break;
	}
	return nRet;
}

LRESULT CTestRunnerWnd::OnNMClickTestRunList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& bHandled)
{
	bHandled = TRUE;
	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);

	LVITEM stItem;
	stItem.iItem = pNMItemActivate->iItem;
	stItem.mask = LVIF_PARAM;
	if ((-1 == stItem.iItem) ||
		(FALSE == m_csTestRunList.SendMessage(LVM_GETITEM, 0, (LPARAM)&stItem)))
		return 0;

	RECT rc;
	rc.top = pNMItemActivate->iSubItem;
	rc.left = LVIR_BOUNDS;
	m_csTestRunList.SendMessage(LVM_GETSUBITEMRECT, pNMItemActivate->iItem, (LPARAM)&rc);

	int x = pNMItemActivate->ptAction.x - rc.left;
	int y = pNMItemActivate->ptAction.y - rc.top;

	WORD nItem = HIWORD(stItem.lParam);
	WORD nGroup = LOWORD(stItem.lParam);

	if ((0xFFFF != nGroup) &&
		(pNMItemActivate->iSubItem == ciImage))
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);
		if (0xFFFF == nItem)
		{ //test group
			if (x > 1 &&  x < 12 && y > 1 && y < 12)
			{
				rGroup.bExpanded = !rGroup.bExpanded;
				LRESULT nTopIndex = m_csTestRunList.SendMessage(LVM_GETTOPINDEX);
				
				Synchronize(nGroup);

				m_csTestRunList.SendMessage(LVM_SCROLL, 0, nTopIndex * 16);


			} else if ((rGroup.bDisabled == FALSE) && (x > 16 && x < 40 && y > 0 && y < 14))
			{
				rGroup.bChecked = !rGroup.bChecked;

				for (size_t n = 0; n < rGroup.aTests.GetCount(); ++n)
					rGroup.aTests.GetAt(n).bChecked = rGroup.bChecked;
				m_csTestRunList.RedrawWindow();
			}

		} else
		{ //rest run
			TestRun & rTestRun = rGroup.aTests.GetAt(nItem);
			if ((rTestRun.bDisabled == FALSE) && (x > 16 && x < 40 && y > 0 && y < 14))
			{
				rTestRun.bChecked = !rTestRun.bChecked;
				if (rTestRun.bChecked)
					rGroup.bChecked = TRUE;
				m_csTestRunList.RedrawWindow();
			}
		}
	}
	
	return 0;
}

LRESULT CTestRunnerWnd::OnLvnKeydownTestRunList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& bHandled)
{
	bHandled = TRUE;

	LPNMLVKEYDOWN pLVKeyDown = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);
	LVITEM stItem;
	stItem.mask = LVIF_PARAM;

	stItem.iItem = static_cast<int>(m_csTestRunList.SendMessage(LVM_GETSELECTIONMARK));
	if ((-1 != stItem.iItem) && 
		m_csTestRunList.SendMessage(LVM_GETITEM, 0, (LPARAM)&stItem))
	{
		WORD nItem = HIWORD(stItem.lParam);
		WORD nGroup = LOWORD(stItem.lParam);

		switch(pLVKeyDown->wVKey)
		{
		case VK_RIGHT:
			ExpandGroup(nGroup);
			break;
		case VK_LEFT:
			CollapseGroup(nGroup);
			break;
		case VK_SPACE:
			ToggleCheckInTestRunList(nGroup, nItem);
			break;
		case 'C' /*VK_C*/:
		case VK_INSERT:
			{
				if ((0x8000 & GetKeyState(VK_LCONTROL)) || 
					(0x8000 & GetKeyState(VK_RCONTROL)))
				{
					CopyIntoClipboard(nGroup, nItem);
				}
			}
			break;
		}
	}

	return 0;
}

void CTestRunnerWnd::ExpandGroup(WORD nGroup)
{
	if (0xFFFF != nGroup)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);
		if (rGroup.bExpanded == FALSE)
		{
			rGroup.bExpanded = TRUE;
			LRESULT nTopIndex = m_csTestRunList.SendMessage(LVM_GETTOPINDEX);
		
			int nSelItem = Synchronize(nGroup);

			m_csTestRunList.SendMessage(LVM_SCROLL, 0, nTopIndex * 16);
			m_csTestRunList.SendMessage(LVM_SETSELECTIONMARK, 0, nSelItem);
		}
	}
}

void CTestRunnerWnd::CollapseGroup(WORD nGroup)
{
	if (0xFFFF != nGroup)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);
		if (rGroup.bExpanded == TRUE)
		{
			rGroup.bExpanded = FALSE;
			LRESULT nTopIndex = m_csTestRunList.SendMessage(LVM_GETTOPINDEX);
		
			int nSelItem = Synchronize(nGroup);

			m_csTestRunList.SendMessage(LVM_SCROLL, 0, nTopIndex * 16);
			m_csTestRunList.SendMessage(LVM_SETSELECTIONMARK, 0, nSelItem);
		}
	}
}

void CTestRunnerWnd::ToggleCheckInTestRunList(WORD nGroup, WORD nItem)
{
	if (0xFFFF != nGroup)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);
		if (0xFFFF == nItem)
		{ //test group
			if(rGroup.bDisabled == FALSE)
			{
				rGroup.bChecked = !rGroup.bChecked;
				
				for (size_t n = 0; n < rGroup.aTests.GetCount(); ++n)
					rGroup.aTests.GetAt(n).bChecked = rGroup.bChecked;
				
				m_csTestRunList.RedrawWindow();
			}

		} else
		{ //rest run
			TestRun & rTestRun = rGroup.aTests.GetAt(nItem);
			if (rTestRun.bDisabled == FALSE)
			{
				rTestRun.bChecked = !rTestRun.bChecked;
				if (rTestRun.bChecked)
					rGroup.bChecked = TRUE;
				m_csTestRunList.RedrawWindow();
			}
		}
	}
}

void CTestRunnerWnd::CopyIntoClipboard(WORD nGroup, WORD nItem)
{
	if (0xFFFF != nGroup)
	{
		CAtlStringW wstrMessage, wstrHtmlMessage;
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);

		wstrMessage = L"Test Group: ";
		wstrHtmlMessage = L"Test Group: <b>";
		wstrMessage.Append(rGroup.wstrName);
		wstrHtmlMessage.Append(rGroup.wstrName);

		if (0xFFFF == nItem)
		{ //test group
			wstrMessage.Append(L"\r\nStatus: "); 
			wstrHtmlMessage.Append(L"</b><br>Status: <font color=\""); 
			switch(rGroup.eStatus)
			{
			case Pending:
			case Running:
				wstrHtmlMessage.Append(L"black");
				break;
			case Stopped:
			case Warning:
			case Inconclusive:
				wstrHtmlMessage.Append(L"maroon");
				break;
			case Succeeded:
				wstrHtmlMessage.Append(L"blue");
				break;
			case Failed:
				wstrHtmlMessage.Append(L"red");
				break;
			}
			wstrHtmlMessage.Append(L"\">");
			wstrHtmlMessage.Append(GetRunStatusName(rGroup.eStatus));

			wstrMessage.Append(GetRunStatusName(rGroup.eStatus));
			wstrHtmlMessage.Append(L"</font><br>Report: ");
			wstrMessage.Append(L"\r\nReport: ");
			wstrHtmlMessage.Append(rGroup.wstrReport);
			wstrMessage.Append(rGroup.wstrReport);
			wstrMessage.Append(L"\r\nTime: ");
			wstrHtmlMessage.Append(L"<br>Time: ");
			if (rGroup.tDuration)
			{
				CTimeSpan sp1(rGroup.tDuration);
				CAtlStringW wstr;
				wstr.Format(L"%02i:%02i:%02i", sp1.GetHours(), sp1.GetMinutes(), sp1.GetSeconds());
				wstrMessage.Append(wstr);
				wstrHtmlMessage.Append(wstr);
			}
			else
			{
				wstrMessage.Append(L"-");
				wstrHtmlMessage.Append(L"-");
			}
		} else
		{ //rest run
			TestRun & rTestRun = rGroup.aTests.GetAt(nItem);

			wstrMessage.Append(L"Test: ");
			wstrHtmlMessage.Append(L"</b><br>Test: <b>");
			wstrMessage.Append(rTestRun.wstrName);
			wstrHtmlMessage.Append(rTestRun.wstrName);
			wstrMessage.Append(L"\r\nStatus: "); 
			wstrHtmlMessage.Append(L"</b><br>Status: <font color=\""); 
			switch(rTestRun.eStatus)
			{
			default:
			//case Pending:
			//case Running:
				wstrHtmlMessage.Append(L"black");
				break;
			case Stopped:
			case Warning:
			case Inconclusive:
				wstrHtmlMessage.Append(L"maroon");
				break;
			case Succeeded:
				wstrHtmlMessage.Append(L"blue");
				break;
			case Failed:
				wstrHtmlMessage.Append(L"red");
				break;
			}
			wstrHtmlMessage.Append(L"\">");
			wstrHtmlMessage.Append(GetRunStatusName(rTestRun.eStatus));

			wstrMessage.Append(GetRunStatusName(rTestRun.eStatus));
			wstrHtmlMessage.Append(L"</font><br>Report: ");
			wstrMessage.Append(L"\r\nReport: ");
			wstrHtmlMessage.Append(rTestRun.wstrReport);
			wstrMessage.Append(rTestRun.wstrReport);
			wstrMessage.Append(L"\r\nTime: ");
			wstrHtmlMessage.Append(L"<br>Time: ");
			if (rTestRun.tDuration)
			{
				CTimeSpan sp1(rTestRun.tDuration);
				CAtlStringW wstr;
				wstr.Format(L"%02i:%02i:%02i", sp1.GetHours(), sp1.GetMinutes(), sp1.GetSeconds());
				wstrMessage.Append(wstr);
				wstrHtmlMessage.Append(wstr);
			}
			else
			{
				wstrMessage.Append(L"-");
				wstrHtmlMessage.Append(L"-");
			}
		}

		CopyIntoClipboardMessage(wstrMessage, wstrHtmlMessage);
	}
}

void CTestRunnerWnd::CopyIntoClipboardMessage(const CAtlStringW & wstrMessage, const CAtlStringW & wstrHtmlMessage)
{
	if (OpenClipboard())
	{
		EmptyClipboard();

		int nLen = wstrMessage.GetLength() + 1;
		HGLOBAL hUnicodeTextMem = GlobalAlloc(GMEM_MOVEABLE, sizeof(wchar_t) * nLen );
		if (hUnicodeTextMem)
		{
			wcscpy_s((LPWSTR)GlobalLock(hUnicodeTextMem), nLen, wstrMessage);
			GlobalUnlock(hUnicodeTextMem);

			SetClipboardData(CF_UNICODETEXT, hUnicodeTextMem);

			GlobalFree(hUnicodeTextMem);
		}

		CAtlStringA strMessage(wstrMessage.GetString());

		HGLOBAL hTextMem = GlobalAlloc(GMEM_MOVEABLE, nLen );
		if (hTextMem)
		{
			strcpy_s((LPSTR)GlobalLock(hTextMem), nLen, strMessage);
			GlobalUnlock(hTextMem);

			SetClipboardData(CF_TEXT, hTextMem);

			GlobalFree(hTextMem);
		}

		CAtlStringA strHtmlMessage;
		PrepareHTMLClipboardText(wstrHtmlMessage, strHtmlMessage);
		nLen = strHtmlMessage.GetLength() + 1;
		HGLOBAL hHtmlMem = GlobalAlloc(GMEM_MOVEABLE, nLen);
		if (hTextMem)
		{
			strcpy_s((LPSTR)GlobalLock(hHtmlMem), nLen, strHtmlMessage);
			GlobalUnlock(hHtmlMem);

			UINT CF_HTML = RegisterClipboardFormatW(L"HTML Format");

			SetClipboardData(CF_HTML, hHtmlMem);

			GlobalFree(hHtmlMem);
		}

		CloseClipboard();
	}
}

void CTestRunnerWnd::PrepareHTMLClipboardText(const CAtlStringW & wstrMessage, CAtlStringA & csRetVal)
{
	csRetVal.Empty();

	CStringA csUtf8;
	int nLen = wstrMessage.GetLength() + 1;

	int nUtf8BufSize = WideCharToMultiByte( CP_UTF8, 0,
		wstrMessage.GetString(), nLen,
		NULL, 0, NULL, NULL );
	if( nUtf8BufSize > 0 )
	{
		int nRet = WideCharToMultiByte( CP_UTF8, 0,
			wstrMessage.GetString(), nLen,
			csUtf8.GetBuffer( nUtf8BufSize), nUtf8BufSize,
			NULL, NULL );
		csUtf8.ReleaseBuffer();

		if( 0 != nRet )
		{
			//csRetVal.Format( "Version:0.9\r\n"\			//13 - length
			//				 "StartHTML:00000097\r\n"\		//20
			//				 "EndHTML:%08u\r\n"\			//18
			//				 "StartFragment:00000111\r\n"\	//24
			//				 "EndFragment:%08u\r\n"\		//22
			//				 "<html><body>\r\n"\			//14
			//				 "<!--StartFragment -->\r\n"\	//23 / sum = 134
			//				 "%s"\
			//				 "<!--EndFragment-->\r\n"\		//20
			//				 "</body></html>",				//14


			csRetVal.Format( "Version:0.9\r\n"\
							 "StartHTML:00000097\r\n"\
							 "EndHTML:%08u\r\n"\
							 "StartFragment:00000111\r\n"\
							 "EndFragment:%08u\r\n"\
							 "<html><body>\r\n"\
							 "<!--StartFragment -->\r\n"\
							 "%s"\
							 "<!--EndFragment-->\r\n"\
							 "</body></html>",
							 csUtf8.GetLength() + 134 + 34,
							 csUtf8.GetLength() + 134,
							 csUtf8.GetString() );
		}
	}
}

LRESULT CTestRunnerWnd::OnNMRClickTestRunList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& bHandled)
{
	bHandled = TRUE;

	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	
	LVITEM stItem;
	stItem.iItem = pNMItemActivate->iItem;
	stItem.mask = LVIF_PARAM;
	if ((-1 == stItem.iItem) ||
		(FALSE == m_csTestRunList.SendMessage(LVM_GETITEM, 0, (LPARAM)&stItem)))
		return 1;

	WORD nItem = HIWORD(stItem.lParam);
	WORD nGroup = LOWORD(stItem.lParam);

	if (0xFFFF != nGroup)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);

		HMENU hMenu = LoadMenu(_AtlModule.GetResourceInstance(), 
			MAKEINTRESOURCE(IDR_TESTTREEPOPUP));

		m_csTestRunList.ClientToScreen(&pNMItemActivate->ptAction);

		if (hMenu)
		{
			HMENU hPopup = GetSubMenu(hMenu, 0);
			MENUITEMINFO sMenuItemInfo;
			sMenuItemInfo.cbSize = sizeof(sMenuItemInfo);
			sMenuItemInfo.fMask = MIIM_STATE;
			sMenuItemInfo.fState = MFS_GRAYED;
			
			if (0xFFFF == nItem)
			{
				RemoveMenu(hPopup, rGroup.bDisabled ? ID_TESTTREEPOPUP_DISABLE : ID_TESTTREEPOPUP_ENABLE, MF_BYCOMMAND);
				RemoveMenu(hPopup, rGroup.bChecked ? ID_TESTTREEPOPUP_CHECK : ID_TESTTREEPOPUP_UNCHECK, MF_BYCOMMAND);
				if (rGroup.bDisabled)
					SetMenuItemInfo(hPopup, rGroup.bChecked ? ID_TESTTREEPOPUP_UNCHECK : ID_TESTTREEPOPUP_CHECK, FALSE, &sMenuItemInfo);

				BOOL bCmd = TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, pNMItemActivate->ptAction.x, pNMItemActivate->ptAction.y, 0, m_hWnd, NULL);
				switch(bCmd)
				{
				case ID_TESTTREEPOPUP_ENABLE:
				case ID_TESTTREEPOPUP_DISABLE:
					{
						rGroup.bDisabled = !rGroup.bDisabled;
						for (size_t n = 0; n < rGroup.aTests.GetCount(); ++n)
							rGroup.aTests.GetAt(n).bDisabled = rGroup.bDisabled;
					}
					break;
				case ID_TESTTREEPOPUP_UNCHECK:
				case ID_TESTTREEPOPUP_CHECK:
					{
						rGroup.bChecked = !rGroup.bChecked;
						for (size_t n = 0; n < rGroup.aTests.GetCount(); ++n)
							rGroup.aTests.GetAt(n).bChecked = rGroup.bChecked;
					}
					break;
				case ID_TESTTREEPOPUP_CHECKALL:
				case ID_TESTTREEPOPUP_UNCHECKALL:
					CheckUncheckAll(bCmd == ID_TESTTREEPOPUP_CHECKALL);
					break;
				case ID_TESTTREEPOPUP_COPY:
					CopyIntoClipboard(nGroup, nItem);
					break;
				}
			} else
			{
				TestRun & rTestRun = rGroup.aTests.GetAt(nItem);
				RemoveMenu(hPopup, rTestRun.bDisabled ? ID_TESTTREEPOPUP_DISABLE : ID_TESTTREEPOPUP_ENABLE, MF_BYCOMMAND);
				RemoveMenu(hPopup, rTestRun.bChecked ? ID_TESTTREEPOPUP_CHECK : ID_TESTTREEPOPUP_UNCHECK, MF_BYCOMMAND);
				if (rTestRun.bDisabled)
					SetMenuItemInfo(hPopup, rTestRun.bChecked ? ID_TESTTREEPOPUP_UNCHECK : ID_TESTTREEPOPUP_CHECK, FALSE, &sMenuItemInfo);

				BOOL bCmd = TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, pNMItemActivate->ptAction.x, pNMItemActivate->ptAction.y, 0, m_hWnd, NULL);
				switch(bCmd)
				{
				case ID_TESTTREEPOPUP_ENABLE:
				case ID_TESTTREEPOPUP_DISABLE:
					{
						rTestRun.bDisabled = !rTestRun.bDisabled;
						if (rTestRun.bDisabled == FALSE)
							rGroup.bDisabled = FALSE;
					}
					break;
				case ID_TESTTREEPOPUP_UNCHECK:
				case ID_TESTTREEPOPUP_CHECK:
					{
						rTestRun.bChecked = !rTestRun.bChecked;
						if ((FALSE == rGroup.bChecked) && (rTestRun.bChecked))
							rGroup.bChecked = TRUE;
					}
					break;
				case ID_TESTTREEPOPUP_CHECKALL:
				case ID_TESTTREEPOPUP_UNCHECKALL:
					CheckUncheckAll(bCmd == ID_TESTTREEPOPUP_CHECKALL);
					break;
				case ID_TESTTREEPOPUP_COPY:
					CopyIntoClipboard(nGroup, nItem);
					break;
				}
			}
			m_csTestRunList.RedrawWindow();

			DestroyMenu(hMenu);
		}
	}

	return 1;
}

LRESULT CTestRunnerWnd::OnClientNotify(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	bHandled = TRUE;
	if (m_pIExchangeControl)
		m_pIExchangeControl->TestAppNotify(wParam, lParam);

	return 0;
}

LRESULT CTestRunnerWnd::OnMonitorThreadNotify(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{	//a client process was terminated before server receive the end notification
	bHandled = TRUE;
	//get dump file path
	CAtlStringW strPath = m_csTestRunMonitorThread.GetProcessFileName();
	strPath.Append(L".dmp");
	//stop tracking the client application
	EndNotification();
	
	if (PathFileExistsW(strPath))
	{
		//try to load dmp file
		ShellExecuteW(NULL, L"open", strPath, NULL, NULL, SW_SHOW);
	}

	return 0;
}

LRESULT CTestRunnerWnd::OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
	switch(wParam)
	{
	case IDC_BT_REFRESH:
		KillTimer(wParam);
		return OnBnClickedBtRefresh(0,0,0, bHandled);
	}
	return 0;
}

void CTestRunnerWnd::CheckUncheckAll(BOOL bCheck)
{
	for (size_t n = 0; n < GetTestGroups().GetCount(); ++n)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(n);
		if (rGroup.bDisabled) continue;
		rGroup.bChecked = bCheck;

		for (size_t n1 = 0; n1 < rGroup.aTests.GetCount(); ++n1)
		{
			TestRun & rTestRun = rGroup.aTests.GetAt(n1);
			if (rTestRun.bDisabled) continue;
			rTestRun.bChecked = bCheck;
		}
	}
}

void CTestRunnerWnd::ResetParameters(TestPackTransData & r)
{
	//reset parameters in the client's test set
	for (size_t t = 0; t < r.aTestGroups.GetCount(); ++t)
	{
		TestGroupTransData & rTestGroupTransData = r.aTestGroups.GetAt(t);
		rTestGroupTransData.bRun = FALSE;
		rTestGroupTransData.lParam = 0xFFFFFFFF;
		for (size_t t1 = 0; t1 < rTestGroupTransData.aTestRuns.GetCount(); ++t1)
		{
			TestRunTransData & rTestRunTransData = rTestGroupTransData.aTestRuns.GetAt(t1);
			rTestRunTransData.bRun = FALSE;
			rTestRunTransData.lParam = 0xFFFFFFF;
		}
	}
}

void CTestRunnerWnd::SychronizeWithClientTestSet(TestPackTransData & r, CAtlArray<TestGroupRun> & rServerGroups, BOOL bAll)
{
	ResetParameters(r);
	
	//mark groups and tests that not present in the client's test set and fill parameters in the client's test set
	for (size_t n = 0; n < rServerGroups.GetCount(); ++n)
	{
		bool bIsGroupFound = false;
		TestGroupRun & rGroup = rServerGroups.GetAt(n);
		rGroup.wstrReport.Empty();
		rGroup.tDuration = 0;

		for (size_t n1 = 0; n1 < rGroup.aTests.GetCount(); ++n1)
		{
			TestRun & rTestRun = rGroup.aTests.GetAt(n1);
			rTestRun.wstrReport.Empty();
			rTestRun.tDuration = 0;
		}

		for (size_t t = 0; t < (DWORD)r.aTestGroups.GetCount(); ++t)
		{
			TestGroupTransData & rTestGroupTransData = r.aTestGroups.GetAt(t);
			if (rGroup.wstrName == rTestGroupTransData.wstrName)
			{
				bIsGroupFound = true;
				rTestGroupTransData.bRun = (rGroup.bDisabled == FALSE) && (bAll || rGroup.bChecked);
				rTestGroupTransData.lParam = (DWORD)n;

				rGroup.eStatus = Pending;

				
				for (size_t n1 = 0; n1 < rGroup.aTests.GetCount(); ++n1)
				{
					TestRun & rTestRun = rGroup.aTests.GetAt(n1);
					bool bIsUnitTestFound = false;	
					for (size_t t1 = 0; t1 < rTestGroupTransData.aTestRuns.GetCount(); ++t1)
					{
						TestRunTransData & rTestRunTransData = rTestGroupTransData.aTestRuns.GetAt(t1);
						if (rTestRun.wstrName == rTestRunTransData.wstrName)
						{
							bIsUnitTestFound = true;
							rTestRun.eStatus = Pending;
							rTestRunTransData.bRun = (rTestGroupTransData.bRun) &&  (rTestRun.bDisabled == FALSE) && (bAll || rTestRun.bChecked);
							rTestRunTransData.lParam = (DWORD)n1;
						}
					}

					if (bIsUnitTestFound == false)
						rTestRun.eStatus = Inconclusive;
				}

				//add to the server test group unit tests from client that have not been presented
				for (size_t t1 = 0; t1 < rTestGroupTransData.aTestRuns.GetCount(); ++t1)
				{
					TestRunTransData & rTestRunTransData = rTestGroupTransData.aTestRuns.GetAt(t1);
					if (rTestRunTransData.lParam == -1)
					{
						rTestRunTransData.lParam = (DWORD)rGroup.aTests.Add();
						rTestRunTransData.bRun = bAll;
						rGroup.aTests.GetAt(rTestRunTransData.lParam).wstrName = rTestRunTransData.wstrName;
					}
				}
				break;
			}
		}

		if (bIsGroupFound == false)
		{	//all test are inconclusive
			rGroup.eStatus = Inconclusive;
			for (size_t n1 = 0; n1 < rGroup.aTests.GetCount(); ++n1)
				rGroup.aTests.GetAt(n1).eStatus = Inconclusive;
		}
	}

	//add groups to the server list
	for (size_t t = 0; t < r.aTestGroups.GetCount(); ++t)
	{
		TestGroupTransData & rTestGroupTransData = r.aTestGroups.GetAt(t);
		if (rTestGroupTransData.lParam == -1)
		{
			rTestGroupTransData.lParam = (DWORD)rServerGroups.Add();
			rTestGroupTransData.bRun = bAll;
			TestGroupRun & rGroup = rServerGroups.GetAt(rTestGroupTransData.lParam);
			rGroup.wstrName = rTestGroupTransData.wstrName;

			for (size_t t1 = 0; t1 < rTestGroupTransData.aTestRuns.GetCount(); ++t1)
			{
				TestRunTransData & rTestRunTransData = rTestGroupTransData.aTestRuns.GetAt(t1);
				rTestRunTransData.lParam = (DWORD)rGroup.aTests.Add();
				rTestRunTransData.bRun = bAll;
				rGroup.aTests.GetAt(rTestRunTransData.lParam).wstrName = rTestRunTransData.wstrName;
			}
		}
	}
}

void CTestRunnerWnd::StartNotification(DWORD dwTestAppProcessId)
{
	m_btDebugAll.EnableWindow(FALSE);
	m_btDebugChecked.EnableWindow(FALSE);
	m_btRunAll.EnableWindow(FALSE);
	m_btRunChecked.EnableWindow(FALSE);

	m_csTestRunMonitorThread.Start(m_hWnd, WM_MONITORTHREADNOTIFY, dwTestAppProcessId);
}

void CTestRunnerWnd::TestSet(TestPackTransData & rTestSet)
{
	SychronizeWithClientTestSet(rTestSet, GetTestGroups(), m_bAll);
	Synchronize();
}

void CTestRunnerWnd::GroupStartNotification(DWORD nGroupParam)
{
	try
	{
		GetTestGroups().GetAt(nGroupParam).eStatus = Running;
		Synchronize();
	}
	catch(CAtlException&)
	{
		EndNotification();
	}
}

void CTestRunnerWnd::TestStartNotification(DWORD nGroupParam, DWORD nTestParam)
{
	try
	{
		GetTestGroups().GetAt(nGroupParam).aTests.GetAt(nTestParam).eStatus = Running;
	}
	catch(CAtlException&)
	{
		EndNotification();
	}
}

void CTestRunnerWnd::TestEndNotification(DWORD nGroupParam, DWORD nTestParam, ERunStatus eRunStatus, __time64_t tDuration, LPCWSTR lpReport)
{
	try
	{
		TestRun & rTestRun = GetTestGroups().GetAt(nGroupParam).aTests.GetAt(nTestParam);

		rTestRun.eStatus = eRunStatus;
		rTestRun.tDuration = tDuration;
		rTestRun.wstrReport = lpReport;

		Synchronize();
	}
	catch(CAtlException&)
	{
		EndNotification();
	}
}

void CTestRunnerWnd::GroupEndNotification(DWORD nGroupParam, ERunStatus eRunStatus, __time64_t tDuration, LPCWSTR lpReport)
{
	try
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroupParam);

		rGroup.eStatus = eRunStatus;
		rGroup.tDuration = tDuration;
		rGroup.wstrReport = lpReport;

		Synchronize();
	}
	catch(CAtlException&)
	{
		EndNotification();
	}
}

void CTestRunnerWnd::EndNotification()
{
	m_csTestRunMonitorThread.Stop();

	//find a test that is still in the run state and change it to the stop one
	for (size_t tGroup = 0; tGroup < GetTestGroups().GetCount(); ++tGroup)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(tGroup);
		if (rGroup.eStatus == Running)
		{
			rGroup.eStatus = Stopped;
			for (size_t tTest = 0; tTest < rGroup.aTests.GetCount(); ++tTest)
			{
				TestRun & rTestRun = rGroup.aTests.GetAt(tTest);
				if (rTestRun.eStatus == Running)
				{
					rTestRun.eStatus = Stopped;
					break;
				}
			}
			Synchronize();
			break;
		}
	}

	m_btDebugAll.EnableWindow(TRUE);
	m_btDebugChecked.EnableWindow(TRUE);
	m_btRunAll.EnableWindow(TRUE);
	m_btRunChecked.EnableWindow(TRUE);
}

void CTestRunnerWnd::AbortNotification()
{
	EndNotification();
}

void CTestRunnerWnd::Start(BOOL bDebug, BOOL bAll)
{
	EnvDTE::ProjectPtr spCurSelProject;
	m_bAll = bAll;

	if ((m_pIExchangeControl) &&
		GetCurSelTestProject(spCurSelProject))
	{
		m_pIExchangeControl->Start(spCurSelProject, bDebug);
	}
}


LRESULT CTestRunnerWnd::OnDblClickTestRunList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& bHandled)
{
	bHandled = TRUE;

	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	LVITEM stItem;
	stItem.iItem = pNMItemActivate->iItem;
	stItem.mask = LVIF_PARAM;
	if ((-1 == stItem.iItem) ||
		(FALSE == m_csTestRunList.SendMessage(LVM_GETITEM, 0, (LPARAM)&stItem)))
		return 1;

	WORD nItem = HIWORD(stItem.lParam);
	WORD nGroup = LOWORD(stItem.lParam);

	if (0xFFFF != nGroup)
	{
		TestGroupRun & rGroup = GetTestGroups().GetAt(nGroup);
		if (0xFFFF == nItem)
			NavigateToFunction(rGroup.wstrName, rGroup.wstrName); //open a test group constructor
		else
			NavigateToFunction(rGroup.wstrName, rGroup.aTests.GetAt(nItem).wstrName);
	}

	return 0;
}

void CTestRunnerWnd::NavigateToFunction(LPCWSTR lpClassName, LPCWSTR lpFunctionName)
{
	EnvDTE::ProjectPtr spCurSelProject;
	EnvDTE::CodeModelPtr spCodeModel;
	VCCodeModelLibrary::VCCodeModelPtr spVCCodeModel;
	EnvDTE::CodeElementsPtr spCodeElements;
	VCCodeModelLibrary::VCCodeElementsPtr spVCCodeElements;
	IDispatchPtr spDispatch;
	VCCodeModelLibrary::VCCodeClassPtr spVCCodeClass;
	VCCodeModelLibrary::VCCodeFunctionPtr spVCCodeFunction;
	CComBSTR bstrFile;
	EnvDTE::ItemOperationsPtr spItemOperations;
	EnvDTE::WindowPtr spWindow;
	EnvDTE::TextPanePtr spTextPane;
	EnvDTE::TextPointPtr spTextPoint;
	EnvDTE::TextWindowPtr spTextWindow;

	if (GetCurSelTestProject(spCurSelProject) &&
		SUCCEEDED(spCurSelProject->get_CodeModel(&spCodeModel)) && 
		(spVCCodeModel = spCodeModel) &&
		SUCCEEDED(spVCCodeModel->get_Classes(&spCodeElements)) &&
		(spVCCodeElements = spCodeElements) &&
		SUCCEEDED(spVCCodeElements->Find(CComBSTR(lpClassName), &spDispatch)) &&
		(spVCCodeClass = spDispatch) &&
		SUCCEEDED(spVCCodeClass->get_Functions(&spCodeElements)) &&
		(spVCCodeElements = spCodeElements) &&
		SUCCEEDED(spVCCodeElements->Find(CComBSTR(lpFunctionName), &spDispatch)) &&
		(spVCCodeFunction = spDispatch) &&
		SUCCEEDED(spVCCodeFunction->get_File(&bstrFile)) &&
		SUCCEEDED(m_spDTE->get_ItemOperations(&spItemOperations)) &&
		SUCCEEDED(spItemOperations->OpenFile(bstrFile, CComBSTR(EnvDTE::vsViewKindCode), &spWindow)) &&
		SUCCEEDED(spWindow->get_Object(&spDispatch)) &&
		(spTextWindow = spDispatch) &&
		SUCCEEDED(spTextWindow->get_ActivePane(&spTextPane)) &&
		SUCCEEDED(spVCCodeFunction->get_StartPoint(&spTextPoint))
		)
	{
		VARIANT_BOOL vb;
		spTextPane->TryToShow(spTextPoint, EnvDTE::vsPaneShowTop, CComVariant(), &vb);
	}
}